﻿/* 
* Copyright (c) 2009, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;
using System.Xml.Linq;

namespace Alignment
{
    public class StructureModel2D : StructureModel
    {
        public class LineRendering
        {
            public static LineRendering CreateFromXRNA(string xrnaLine)
            {
                if (xrnaLine != null)
                {
                    if (Regex.IsMatch(xrnaLine, XRNA_BPLineConnectorDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, " ");
                        Debug.Assert(tokens.Length == 8);
                        double thickness;
                        if (Double.TryParse(tokens[1], out thickness))
                        {
                            LineRendering shape = new LineRendering()
                            {
                                Thickness = thickness,
                                Color = ("#" + tokens[2]).PadLeft(7, '0')
                            };
                            return shape;
                        }
                    }
                }
                return null; //Some error was encountered or this was not a valid line. In the future, we would probably throw an error to distinguish
            }

            public double Thickness { get; set; }
            public string Color { get; set; }

            private static string XRNA_BPLineConnectorDefinition = @"!Bl\s\d+.\d{2}\s[0-9a-fA-F]+(\s\d){5}";
        }

        public class CircleRendering
        {
            public static CircleRendering CreateFromXRNA(string xrnaLine)
            {
                if (xrnaLine != null)
                {
                    if (Regex.IsMatch(xrnaLine, XRNA_BPConnectorDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, " ");
                        Debug.Assert(tokens.Length == 7);
                        double radius, thickness;
                        int filled;
                        if (Double.TryParse(tokens[3], out radius) &&
                            Double.TryParse(tokens[4], out thickness) &&
                            Int32.TryParse(tokens[5], out filled))
                        {
                            CircleRendering shape = new CircleRendering()
                            {
                                Filled = (filled == 1) ? false : true,
                                FillColor = "#" + tokens[6].PadLeft(6, '0'),
                                EdgeColor = "#000000",
                                EdgeThickness = thickness,
                                Diameter = 2 * radius
                            };
                            return shape;
                        }
                    }
                    else if (Regex.IsMatch(xrnaLine, XRNA_NTCircleSubstitutionDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, " ");
                        Debug.Assert(tokens.Length == 7);
                        double radius, thickness;
                        int filled;
                        if (Double.TryParse(tokens[3], out radius) &&
                            Double.TryParse(tokens[4], out thickness) &&
                            Int32.TryParse(tokens[5], out filled))
                        {
                            CircleRendering shape = new CircleRendering()
                            {
                                Filled = (filled == 1) ? false : true,
                                FillColor = "#" + tokens[6].PadLeft(6, '0'),
                                EdgeColor = "#000000",
                                EdgeThickness = thickness,
                                Diameter = 2 * radius
                            };
                            return shape;
                        }
                    }
                }
                return null;
            }

            public bool Filled { get; set; }
            public string FillColor { get; set; }
            public double Diameter { get; set; }
            public double EdgeThickness { get; set; }
            public string EdgeColor { get; set; }

            public CircleRendering() { }

            private static string XRNA_BPConnectorDefinition = @"!Bc\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d\s[a-zA-Z0-9]+";
            private static string XRNA_NTCircleSubstitutionDefinition = @"!c\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d\s[a-zA-Z0-9]+";
        }

        public class NucleotideRendering
        {
            public static NucleotideRendering CreateFromXRNA(int typefaceID, double fontSize, string color)
            {
                return new NucleotideRendering(typefaceID, fontSize, color);
            }

            public string Font { get; set; }
            public double FontSize { get; set; }
            public string FontStyle { get; set; }
            public string FontWeight { get; set; }
            public string Color { get; set; }

            public NucleotideRendering() { }

            private NucleotideRendering(int typefaceID, double fontSize, string color)
            {
                StructureModel2D.XRNATypeface typeface = StructureModel2D.ParseXRNACode(typefaceID);
                Font = typeface.Font;
                FontStyle = typeface.FontStyle;
                FontWeight = typeface.FontWeight;
                FontSize = fontSize;
                Color = color;
            }
        }

        public class StructureModelBasePair
        {
            public int FivePrimeIndex { get; private set; }
            public int ThreePrimeIndex { get; private set; }
            public BasePairConnectorShape ConnectorRendering { get; private set; }
            public bool? ConnectorVisible { get; set; }

            public StructureModelBasePair(int fivePrimeIndex, int threePrimeIndex,
                BasePairConnectorShape connectorRendering)
            {
                ConnectorVisible = null; //This is so that we don't end up writing out Visible for all those that are since its a default
                FivePrimeIndex = fivePrimeIndex;
                ThreePrimeIndex = threePrimeIndex;
                ConnectorRendering = connectorRendering;
            }
        }

        public class StructureModelNucleotide
        {
            public int Index { get; private set; }
            public char Nucleotide { get; private set; }
            public NucleotideShape Rendering { get; set; }
            public bool? Visible { get; set; }

            public StructureModelNucleotide(int index, char nucleotide)
            {
                Index = index;
                Nucleotide = nucleotide;
                Visible = null; //This is so that we don't end up writing out Visible for all those that are since its a default
            }
        }

        public class SequenceRendering
        {
            public Dictionary<int, NucleotideConnector> RenderingMap
            {
                get { return _renderingMap; }
            }

            public SequenceRendering()
            {
                _renderingMap = new Dictionary<int, NucleotideConnector>();
            }

            private Dictionary<int, NucleotideConnector> _renderingMap;
        }

        public class NucleotideConnector
        {
            public static NucleotideConnector CreateFromXRNA(string xrnaLine, out int index, out int pairedIndex)
            {
                pairedIndex = -1;
                index = -1;
                if (xrnaLine != null && Regex.IsMatch(xrnaLine, XRNA_NTDefinition))
                {
                    string[] tokens = Regex.Split(xrnaLine, " ");
                    Debug.Assert(tokens.Length == 6);
                    //Lets do some basic validation
                    int proposedIndex, proposedPairedIndex;
                    double centerx, centery;
                    if (Int32.TryParse(tokens[0], out proposedIndex) &&
                        Int32.TryParse(tokens[5], out proposedPairedIndex) &&
                        Double.TryParse(tokens[2], out centerx) &&
                        Double.TryParse(tokens[3], out centery))
                    {
                        if (proposedIndex == proposedPairedIndex) return null; //Error, a nucleotide can't be paired to itself
                        if (proposedIndex < 0 || proposedPairedIndex < 0) return null; //Error, an invalid index is specified
                        pairedIndex = proposedPairedIndex - 1;
                        index = proposedIndex - 1;
                        return new NucleotideConnector(tokens[1][0], centerx, centery);
                    }
                }
                return null; //Some error was encountered or this was not a valid line. In the future, we would probably throw an error to distinguish
            }

            public void ConnectTo(NucleotideConnector other)
            {
                RayAngle = Math.PI + Math.Atan2((CenterY - other.CenterY), (CenterX - other.CenterX));
                Magnitude = Math.Sqrt(Math.Pow((other.CenterY - CenterY), 2) + Math.Pow((other.CenterX - CenterX), 2));
            }

            public double RayAngle { get; private set; }
            public double Magnitude { get; private set; }
            public double CenterX { get; private set; }
            public double CenterY { get; private set; }
            public char Nucleotide { get; private set; }

            public NucleotideConnector(char symbol, double xpos, double ypos, double rayAngle, double magnitude)
            {
                CenterX = xpos;
                CenterY = ypos;
                RayAngle = rayAngle;
                Magnitude = magnitude;
            }

            private NucleotideConnector(char symbol, double xpos, double ypos)
            {
                CenterX = xpos;
                CenterY = -1 * ypos;
                RayAngle = 0;
                Magnitude = 0;
                Nucleotide = symbol;
            }
  
            private static string XRNA_NTDefinition = @"\d+\s[AGCUN]\s-?\d+.\d{2}\s-?\d+.\d{2}\s-?[0-9]2?\s\d+";
        }

        public enum NucleotideShapeType { Text, Circle };
        public enum BasePairConnectorShapeType { Line, Circle, Rectangle };

        public class NucleotideShape
        {
            public static NucleotideShape CreateFromXRNA(string xrnaLine)
            {
                if (xrnaLine != null)
                {
                    if (Regex.IsMatch(xrnaLine, XRNA_NTTextRenderingDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, " ");
                        Debug.Assert(tokens.Length == 4);
                        Debug.Assert(!string.IsNullOrEmpty(tokens[0]));
                        int typefaceID;
                        double fontSize;
                        if (Int32.TryParse(tokens[2], out typefaceID) &&
                            Double.TryParse(tokens[1], out fontSize))
                        {
                            string color = "#" + tokens[3].PadLeft(6, '0');
                            NucleotideRendering shape = NucleotideRendering.CreateFromXRNA(typefaceID, fontSize, color);
                            string[] ranges = Regex.Split(Regex.Split(tokens[0], "#")[0], ",");
                            IEnumerable<int> assignedTo = from range in ranges
                                                          from idx in StructureModel2D.ExpandXRNARange(range, true)
                                                          select idx - 1;
                            return new NucleotideShape(null, shape, assignedTo);
                        }
                    }
                }
                return null; //Some error was encountered or this was not a valid line. In the future, we would probably throw an error to distinguish
            }

            public static NucleotideShape CreateFromXRNATextSubstitution(string xrnaLine)
            {
                if (xrnaLine != null)
                {
                    if (Regex.IsMatch(xrnaLine, XRNA_NTTextSubstitutionDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, " ");
                        Debug.Assert(tokens.Length == 5);
                        Debug.Assert(!string.IsNullOrEmpty(tokens[0]));
                        int typefaceID;
                        double fontSize;
                        if (double.TryParse(tokens[1], out fontSize) &&
                            Int32.TryParse(tokens[2], out typefaceID))
                        {
                            string color = "#" + tokens[4].PadLeft(6, '0');
                            NucleotideRendering shape = NucleotideRendering.CreateFromXRNA(typefaceID, fontSize, color);
                            string[] ranges = Regex.Split(Regex.Split(tokens[0], "#")[0], ",");
                            IEnumerable<int> assignedTo = from range in ranges
                                                          from idx in StructureModel2D.ExpandXRNARange(range, true)
                                                          select idx - 1;
                            return new NucleotideShape(null, shape, assignedTo, tokens[3][0]);
                        }
                    }
                }
                return null;
            }

            public static NucleotideShape CreateFromXRNASymbolSubstitution(string xrnaLine)
            {
                if (xrnaLine != null)
                {
                    if (Regex.IsMatch(xrnaLine, XRNA_NTSymbolSubstitutionDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, " ");
                        Debug.Assert(tokens.Length == 4);
                        Debug.Assert(!string.IsNullOrEmpty(tokens[0]));
                        double fontSize;
                        if (double.TryParse(tokens[1], out fontSize))
                        {
                            string color = "#" + tokens[3].PadLeft(6, '0');
                            NucleotideRendering shape = NucleotideRendering.CreateFromXRNA(13, fontSize, color);
                            string[] ranges = Regex.Split(Regex.Split(tokens[0], "#")[0], ",");
                            IEnumerable<int> assignedTo = from range in ranges
                                                          from idx in StructureModel2D.ExpandXRNARange(range, true)
                                                          select idx - 1;
                            return new NucleotideShape(null, shape, assignedTo, tokens[2][0]);
                        }
                    }
                }
                return null;
            }

            public static NucleotideShape CreateFromXRNACircleSubstitution(string xrnaLine)
            {
                if (xrnaLine != null)
                {
                    if (Regex.IsMatch(xrnaLine, XRNA_NTCircleSubstitutionDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, "#");
                        Debug.Assert(tokens.Length == 2);
                        Debug.Assert(!string.IsNullOrEmpty(tokens[0]));
                        CircleRendering shape = CircleRendering.CreateFromXRNA(tokens[1]);
                        string[] ranges = Regex.Split(Regex.Split(tokens[0], "#")[0], ",");
                        IEnumerable<int> assignedTo = from range in ranges
                                                      from idx in StructureModel2D.ExpandXRNARange(range, true)
                                                      select idx - 1;
                        return new NucleotideShape(NucleotideShapeType.Circle, shape, assignedTo);
                    }
                }
                return null;
            }

            public IEnumerable<int> AssignedTo
            {
                get { return _assignedTo; }
                private set { _assignedTo = value; }
            }

            public NucleotideRendering TextShape
            {
                get { return _textShape; }
                private set { _textShape = value; }
            }

            public CircleRendering CircleShape
            {
                get { return _circleShape; }
                private set { _circleShape = value; }
            }

            public bool IsTextSubstitution
            {
                get { return _isTextSubstitution; }
                private set { _isTextSubstitution = value; }
            }

            public char TextSubstitution
            {
                get { return _textSubstitution; }
                private set { _textSubstitution = value; }
            }

            public NucleotideShapeType? RenderingType
            {
                get { return _renderingType; }
                private set { _renderingType = value; }
            }

            public NucleotideShape(NucleotideShapeType? type, CircleRendering shape, IEnumerable<int> assignedTo)
            {
                AssignedTo = assignedTo;
                CircleShape = shape;
                TextShape = null;
                IsTextSubstitution = false;
                RenderingType = type;
            }

            public NucleotideShape(NucleotideShapeType? type, NucleotideRendering shape, IEnumerable<int> assignedTo, char textSubstitution)
            {
                AssignedTo = assignedTo;
                TextShape = shape;
                CircleShape = null;
                IsTextSubstitution = true;
                TextSubstitution = textSubstitution;
                RenderingType = type;
            }

            public NucleotideShape(NucleotideShapeType? type, NucleotideRendering shape, IEnumerable<int> assignedTo)
            {
                AssignedTo = assignedTo;
                TextShape = shape;
                CircleShape = null;
                IsTextSubstitution = false;
                RenderingType = type;
            }

            public NucleotideShape(NucleotideShapeType? type, NucleotideRendering textShape, CircleRendering circleShape, IEnumerable<int> assignedTo)
            {
                AssignedTo = assignedTo;
                TextShape = textShape;
                CircleShape = circleShape;
                IsTextSubstitution = false;
                RenderingType = type;
            }

            public NucleotideShape(NucleotideShapeType? type, NucleotideRendering textShape, CircleRendering circleShape, IEnumerable<int> assignedTo, char textSubstitution)
            {
                AssignedTo = assignedTo;
                TextShape = textShape;
                CircleShape = circleShape;
                IsTextSubstitution = true;
                TextSubstitution = textSubstitution;
                RenderingType = type;
            }

            public NucleotideShape(NucleotideShapeType? type, IEnumerable<int> assignedTo)
            {
                AssignedTo = assignedTo;
                TextShape = null;
                CircleShape = null;
                IsTextSubstitution = false;
                RenderingType = type;
            }

            public NucleotideShape(IEnumerable<int> assignedTo)
            {
                AssignedTo = assignedTo;
                TextShape = null;
                CircleShape = null;
                IsTextSubstitution = false;
                RenderingType = null;
            }

            private IEnumerable<int> _assignedTo;
            private NucleotideRendering _textShape;
            private CircleRendering _circleShape;
            private bool _isTextSubstitution = false;
            private char _textSubstitution;
            private NucleotideShapeType? _renderingType;
            private static string XRNA_NTTextRenderingDefinition = @"[\d+(-?\d+),?]+#!s\s\d+[.\d0]*\s\d+\s[a-fA-F0-9]+";
            private static string XRNA_NTTextSubstitutionDefinition = @"[\d+(-?\d+),?]+#!C\s\d+[.\d0]*\s\d+\s[a-zA-Z]\s[a-fA-F0-9]+";
            private static string XRNA_NTSymbolSubstitutionDefinition = @"[\d+(-?\d+),?]+#!S\s\d+[.\d0]*\s[a-zA-Z]\s[a-fA-F0-9]+";
            private static string XRNA_NTCircleSubstitutionDefinition = @"[\d+(-?\d+),?]+#!c\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d\s[a-zA-Z0-9]+";
        }

        public class BasePairConnectorShape
        {
            public static BasePairConnectorShape CreateFromXRNA(string xrnaLine)
            {
                if (xrnaLine != null)
                {
                    CircleRendering circleConnectorShape = null;
                    LineRendering lineConnectorShape = null;
                    string[] ranges = null;
                    if (Regex.IsMatch(xrnaLine, BPCircleConnectorDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, "#");
                        Debug.Assert(tokens.Length == 2);
                        Debug.Assert(!string.IsNullOrEmpty(tokens[0]));
                        circleConnectorShape = CircleRendering.CreateFromXRNA(tokens[1]);
                        ranges = Regex.Split(tokens[0], ",");
                    }
                    else if (Regex.IsMatch(xrnaLine, BPLineConnectorDefinition))
                    {
                        string[] tokens = Regex.Split(xrnaLine, "#");
                        Debug.Assert(tokens.Length == 2);
                        Debug.Assert(!string.IsNullOrEmpty(tokens[0]));
                        //We need an XRNALineShape and an Enumerable of positions
                        lineConnectorShape = LineRendering.CreateFromXRNA(tokens[1]);
                        ranges = Regex.Split(tokens[0], ",");
                    }

                    if (ranges != null && circleConnectorShape != null)
                    {
                        IEnumerable<int> assignedTo = from range in ranges
                                                      from fpIdx in StructureModel2D.ExpandXRNARange(range, true)
                                                      select fpIdx - 1;
                        return new BasePairConnectorShape(assignedTo)
                        {
                            Size = circleConnectorShape.Diameter,
                            EdgeThickness = circleConnectorShape.EdgeThickness,
                            Filled = circleConnectorShape.Filled,
                            FillColor = circleConnectorShape.FillColor,
                            StrokeColor = circleConnectorShape.EdgeColor,
                            Style = BasePairConnectorShapeType.Circle
                        };
                    }

                    if (ranges != null && lineConnectorShape != null)
                    {
                        IEnumerable<int> assignedTo = from range in ranges
                                                      from fpIdx in StructureModel2D.ExpandXRNARange(range, true)
                                                      select fpIdx - 1;
                        return new BasePairConnectorShape(assignedTo)
                        {
                            Size = lineConnectorShape.Thickness,
                            StrokeColor = lineConnectorShape.Color,
                            Style = BasePairConnectorShapeType.Line
                        };
                    }
                }
                return null;
            }

            public IEnumerable<int> AssignedTo { get; private set; }
            public string FillColor { get; set; }
            public string StrokeColor { get; set; }
            public bool? Filled { get; set; }
            public double EdgeThickness { get; set; }
            public double Size { get; set; }
            public BasePairConnectorShapeType? Style { get; set; }

            public BasePairConnectorShape(IEnumerable<int> assignedTo)
            {
                AssignedTo = assignedTo;
            }

            private static string BPCircleConnectorDefinition = @"[\d+(-?\d+),?]+\s#!Bc\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d+.\d{2}\s\d\s[a-zA-Z0-9]+";
            private static string BPLineConnectorDefinition = @"[\d+(-?\d+),?]+\s#!Bl\s\d+.\d{2}\s[0-9a-fA-F]+(\s\d){5}";
        }

        public static StructureModel2D ParseXRNA(string filename)
        {
            try
            {
                List<string> xrnaFileLines = new List<string>();
                using (var reader = File.OpenText(filename))
                {
                    string line;
                    while (!string.IsNullOrEmpty(line = reader.ReadLine()))
                    {
                        xrnaFileLines.Add(line);
                    }
                }
                return new StructureModel2D(xrnaFileLines);
            }
            catch 
            {
                return null; //If we fail to parse
            }
        }

        public static StructureModel2D FromXML2D(XElement structureModel)
        {
            Debug.Assert(structureModel!=null);
            Debug.Assert(structureModel.Name.LocalName.Equals(SequenceMetadata.StructureModelLabel));

            int sequenceLength = 0;
            if (structureModel.HasAttributes)
            {
                XAttribute renderingAttribute = structureModel.Attribute(SequenceMetadata.StructureModelHasRenderingAttribute);
                if (renderingAttribute != null)
                {
                    bool bHasRendering;
                    if (!bool.TryParse(renderingAttribute.Value, out bHasRendering))
                        bHasRendering = false;

                    if(!bHasRendering)
                        return null; //Can't parse as a two-dimensional rendering;
                }

                XAttribute sequenceLengthAttribute = structureModel.Attribute(SequenceMetadata.StructureModelSequenceLengthAttribute);
                if (sequenceLengthAttribute != null)
                {
                    if (!Int32.TryParse(sequenceLengthAttribute.Value, out sequenceLength))
                        return null;

                    if (sequenceLength < 0)
                        return null;
                }
            }
            else
            {
                return null; //Can't parse as a two-dimensional rendering
            }


            XElement seq = structureModel.Element(SequenceMetadata.StructureModelSequenceLabel);
            XElement rendering = structureModel.Element(SequenceMetadata.StructureModelRenderingLabel);
            XElement pairs = structureModel.Element(SequenceMetadata.StructureModelBasePairsLabel);
            XElement ntprops = structureModel.Element(SequenceMetadata.StructureModelNucleotidePropertiesLabel);

            if (seq != null && rendering != null && pairs != null && ntprops != null)
            {
                StructureModel2D retValue = new StructureModel2D(sequenceLength);

                //1. Check for global level structure model properties.
                XElement strmodelprops = structureModel.Element(SequenceMetadata.StructureModelPropertiesLabel);
                if (strmodelprops != null)
                {
                    XElement strmodelntfont = strmodelprops.Element(SequenceMetadata.StructureModelNucleotideFontLabel);
                    XElement strmodelntfill = strmodelprops.Element(SequenceMetadata.StructureModelNucleotideFillLabel);
                    XElement strmodelntfontsize = strmodelprops.Element(SequenceMetadata.StructureModelNucleotideFontSizeLabel);
                    XElement strmodelntfontstyle = strmodelprops.Element(SequenceMetadata.StructureModelNucleotideFontStyleLabel);
                    XElement strmodelntfontweight = strmodelprops.Element(SequenceMetadata.StructureModelNucleotideFontWeightLabel);

                    StructureModel2D.NucleotideRendering strmodelntrendering = new StructureModel2D.NucleotideRendering();
                    bool bHasSMNtRendering = false;
                    if (strmodelntfont != null && !string.IsNullOrEmpty(strmodelntfont.Value))
                    {
                        strmodelntrendering.Font = strmodelntfont.Value;
                        bHasSMNtRendering = true;
                    }

                    if (strmodelntfontstyle != null && !string.IsNullOrEmpty(strmodelntfontstyle.Value))
                    {
                        strmodelntrendering.FontStyle = strmodelntfontstyle.Value;
                        bHasSMNtRendering = true;
                    }

                    if (strmodelntfontweight != null && !string.IsNullOrEmpty(strmodelntfontweight.Value))
                    {
                        strmodelntrendering.FontWeight = strmodelntfontweight.Value;
                        bHasSMNtRendering = true;
                    }

                    if (strmodelntfill != null && !string.IsNullOrEmpty(strmodelntfill.Value))
                    {
                        strmodelntrendering.Color = strmodelntfill.Value;
                        bHasSMNtRendering = true;
                    }

                    double strmodelfontsizeval;
                    if (strmodelntfontsize != null && double.TryParse(strmodelntfontsize.Value, out strmodelfontsizeval))
                    {
                        strmodelntrendering.FontSize = strmodelfontsizeval;
                        bHasSMNtRendering = true;
                    }

                    if(bHasSMNtRendering)
                        retValue.NucleotideRenderingAtStructureModelLevel = strmodelntrendering;

                    XElement strmodelbpconnstyle = strmodelprops.Element(SequenceMetadata.StructureModelPairConnectorRenderingStyleLabel);
                    XElement strmodelbpconnsize = strmodelprops.Element(SequenceMetadata.StructureModelPairConnectorSizeLabel);
                    XElement strmodelbpconnthickness = strmodelprops.Element(SequenceMetadata.StructureModelPairConnectorStrokeThicknessLabel);
                    XElement strmodelbpconnfill = strmodelprops.Element(SequenceMetadata.StructureModelPairConnectorFillColorLabel);
                    XElement strmodelbpconnfilled = strmodelprops.Element(SequenceMetadata.StructureModelPairConnectorFilledLabel);
                    XElement strmodelbpconnstroke = strmodelprops.Element(SequenceMetadata.StructureModelPairConnectorStrokeColorLabel);

                    StructureModel2D.BasePairConnectorShape strmodelbprendering = new StructureModel2D.BasePairConnectorShape(null);
                    bool bHasSMBpRendering = false;

                    if (strmodelbpconnstyle != null && !string.IsNullOrEmpty(strmodelbpconnstyle.Value))
                    {
                        BasePairConnectorShapeType style = (BasePairConnectorShapeType)Enum.Parse(typeof(BasePairConnectorShapeType), strmodelbpconnstyle.Value);
                        strmodelbprendering.Style = style;
                        bHasSMBpRendering = true;
                    }

                    double connSize;
                    if (strmodelbpconnsize != null && double.TryParse(strmodelbpconnsize.Value, out connSize))
                    {
                        if (connSize > 0.0)
                        {
                            strmodelbprendering.Size = connSize;
                            bHasSMBpRendering = true;
                        }
                    }

                    double connThickness;
                    if (strmodelbpconnthickness != null && double.TryParse(strmodelbpconnthickness.Value, out connThickness))
                    {
                        if (connThickness > 0.0)
                        {
                            strmodelbprendering.EdgeThickness = connThickness;
                            bHasSMBpRendering = true;
                        }
                    }

                    if (strmodelbpconnstroke != null && !string.IsNullOrEmpty(strmodelbpconnstroke.Value))
                    {
                        strmodelbprendering.StrokeColor = strmodelbpconnstroke.Value;
                        bHasSMBpRendering = true;
                    }

                    if (strmodelbpconnfill != null && !string.IsNullOrEmpty(strmodelbpconnfill.Value))
                    {
                        strmodelbprendering.FillColor = strmodelbpconnfill.Value;
                        bHasSMBpRendering = true;
                    }

                    bool connfilled;
                    if (strmodelbpconnfilled != null && bool.TryParse(strmodelbpconnfilled.Value, out connfilled))
                    {
                        strmodelbprendering.Filled = connfilled;
                        bHasSMBpRendering = true;
                    }
                }

                //2. Process the pairs
                var bps = from pair in pairs.Descendants(SequenceMetadata.StructureModelPairLabel)
                          select pair;
                foreach (var bp in bps)
                {
                    int fivePrimeIndex, threePrimeIndex;
                    if (Int32.TryParse(bp.Element(SequenceMetadata.StructureModelPairFivePrimeIndexLabel).Value, out fivePrimeIndex) &&
                        Int32.TryParse(bp.Element(SequenceMetadata.StructureModelPairThreePrimeIndexLabel).Value, out threePrimeIndex))
                    {
                        retValue.Pairs.Add(fivePrimeIndex, threePrimeIndex);

                        XElement bpconnvisible = bp.Element(SequenceMetadata.StructureModelPairConnectorVisibleLabel);
                        XElement bpconnstyle = bp.Element(SequenceMetadata.StructureModelPairConnectorRenderingStyleLabel);
                        XElement bpconnectorprops = bp.Element(SequenceMetadata.StructureModelPairConnectorLabel);

                        BasePairConnectorShape bpConn = null;

                        if (bpconnstyle != null && !string.IsNullOrEmpty(bpconnstyle.Value))
                        {
                            if (bpConn == null)
                                bpConn = new BasePairConnectorShape(null);

                            BasePairConnectorShapeType style = (BasePairConnectorShapeType)Enum.Parse(typeof(BasePairConnectorShapeType), bpconnstyle.Value);
                            bpConn.Style = style;
                        }

                        if (bpconnectorprops != null && !string.IsNullOrEmpty(bpconnectorprops.Value))
                        {
                            XElement bpconnsize = bpconnectorprops.Element(SequenceMetadata.StructureModelPairConnectorSizeLabel);
                            XElement bpconnthickness = bpconnectorprops.Element(SequenceMetadata.StructureModelPairConnectorStrokeThicknessLabel);
                            XElement bpconnfill = bpconnectorprops.Element(SequenceMetadata.StructureModelPairConnectorFillColorLabel);
                            XElement bpconnfilled = bpconnectorprops.Element(SequenceMetadata.StructureModelPairConnectorFilledLabel);
                            XElement bpconnstroke = bpconnectorprops.Element(SequenceMetadata.StructureModelPairConnectorStrokeColorLabel);

                            double connSize;
                            if (bpconnsize != null && double.TryParse(bpconnsize.Value, out connSize))
                            {
                                if (connSize > 0.0)
                                {
                                    if (bpConn == null)
                                        bpConn = new BasePairConnectorShape(null);
                                    bpConn.Size = connSize;
                                }
                            }

                            double connThickness;
                            if (bpconnthickness != null && double.TryParse(bpconnthickness.Value, out connThickness))
                            {
                                if (connThickness > 0.0)
                                {
                                    if (bpConn == null)
                                        bpConn = new BasePairConnectorShape(null);
                                    bpConn.EdgeThickness = connThickness;
                                }
                            }

                            if (bpconnstroke != null && !string.IsNullOrEmpty(bpconnstroke.Value))
                            {
                                if (bpConn == null)
                                    bpConn = new BasePairConnectorShape(null);
                                bpConn.StrokeColor = bpconnstroke.Value;
                            }

                            if (bpconnfill != null && !string.IsNullOrEmpty(bpconnfill.Value))
                            {
                                if (bpConn == null)
                                    bpConn = new BasePairConnectorShape(null);
                                bpConn.FillColor = bpconnfill.Value;
                            }

                            bool connFilled;
                            if (bpconnfilled != null && bool.TryParse(bpconnfilled.Value, out connFilled))
                            {
                                if (bpConn == null)
                                    bpConn = new BasePairConnectorShape(null);
                                bpConn.Filled = connFilled;
                            }
                        }

                        StructureModelBasePair bpObj = null;
                        if (bpConn != null)
                            bpObj = new StructureModelBasePair((fivePrimeIndex - 1), (threePrimeIndex - 1), bpConn);
                        else
                            bpObj = new StructureModelBasePair((fivePrimeIndex - 1), (threePrimeIndex - 1), null);
                        
                        bool connVisible;
                        if (bpconnvisible != null && bool.TryParse(bpconnvisible.Value, out connVisible))
                            bpObj.ConnectorVisible = connVisible;

                        retValue.BasePairs.Add(bpObj);
                    }
                }

                //3. Process the sequence and the rendering map
                int ntIndex = 0;
                foreach (var nt in seq.Value)
                {
                    StructureModel2D.StructureModelNucleotide smnt = new StructureModel2D.StructureModelNucleotide(ntIndex, nt);
                    retValue.Sequence.Add(smnt);
                    ntIndex++;
                }

                if (ntIndex != retValue.SequenceLength) return null; //Some kind of internal error

                var ntvectors = from ntvector in rendering.Descendants(SequenceMetadata.StructureModelRenderingVectorLabel)
                                select ntvector;
                foreach (var ntvector in ntvectors)
                {
                    int vectorNtIndex;
                    if (Int32.TryParse(ntvector.Element(SequenceMetadata.StructureModelRenderingVectorNucleotideIndexLabel).Value, out vectorNtIndex))
                    {
                        double centerX, centerY;//, rayAngle, magnitude;
                        if (Double.TryParse(ntvector.Element(SequenceMetadata.StructureModelRenderingVectorXLabel).Value, out centerX) &&
                            Double.TryParse(ntvector.Element(SequenceMetadata.StructureModelRenderingVectorYLabel).Value, out centerY))
                        {
                            retValue.Rendering.RenderingMap.Add((vectorNtIndex - 1), new StructureModel2D.NucleotideConnector(retValue.Sequence[(vectorNtIndex - 1)].Nucleotide,
                                centerX, centerY, 0.0, 0.0));
                        }
                        else
                        {
                            retValue.Rendering.RenderingMap.Clear();
                            break;
                        }
                    }
                }

                //3.a Wire up the nucleotide connectors
                var allNt = retValue.Rendering.RenderingMap.Keys.OrderBy(k => k);
                foreach (var nt in allNt)
                {
                    int nextNt = (nt + 1);
                    if (nextNt < retValue.Rendering.RenderingMap.Keys.Count)
                    {
                        StructureModel2D.NucleotideConnector ntConn = retValue.Rendering.RenderingMap[nt];
                        StructureModel2D.NucleotideConnector nextNtConn = retValue.Rendering.RenderingMap[nextNt];
                        ntConn.ConnectTo(nextNtConn);
                    }
                }

                //4. Process any nucleotide properties
                var ntpropset = from ntprop in ntprops.Descendants(SequenceMetadata.StructureModelNucleotideLabel)
                                select ntprop;
                foreach (var ntpropobj in ntpropset)
                {
                    int propNtIndex;
                    if (Int32.TryParse(ntpropobj.Element(SequenceMetadata.StructureModelNucleotideIndexLabel).Value, out propNtIndex))
                    {
                        if ((propNtIndex - 1) >= 0 && (propNtIndex - 1) < retValue.SequenceLength)
                        {
                            StructureModel2D.StructureModelNucleotide nt = retValue.Sequence[(propNtIndex - 1)];
                            var ntvis = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideVisibilityLabel);
                            bool ntVisibility;
                            if (ntvis != null &&
                                bool.TryParse(ntvis.Value, out ntVisibility))
                            {
                                nt.Visible = ntVisibility;
                            }

                            NucleotideShapeType? ntRenderingStyle = null;
                            var ntstyle = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideRenderingStyleLabel);
                            if (ntstyle != null &&
                                !ntstyle.IsEmpty)
                            {
                                object style = Enum.Parse(typeof(NucleotideShapeType), ntstyle.Value);
                                if (style != null)
                                {
                                    ntRenderingStyle = (NucleotideShapeType)style;
                                }
                            }

                            bool isNtSubstituted = false;
                            char ntSubstitutionVal = ' ';
                            var ntsubstitution = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideSubstitutionLabel);
                            if (ntsubstitution != null &&
                                !ntsubstitution.IsEmpty)
                            {
                                isNtSubstituted = true;
                                ntSubstitutionVal = ntsubstitution.Value[0];
                            }

                            NucleotideRendering customntrendering = null;
                            XElement customntfont = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideFontLabel);
                            XElement customntfill = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideFillLabel);
                            XElement customntfontsize = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideFontSizeLabel);
                            XElement customntfontstyle = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideFontStyleLabel);
                            XElement customntfontweight = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideFontWeightLabel);

                            if (customntfont != null &&
                                !customntfont.IsEmpty)
                            {
                                if (customntrendering == null)
                                    customntrendering = new NucleotideRendering();

                                customntrendering.Font = customntfont.Value;
                            }

                            if (customntfill != null &&
                                !customntfill.IsEmpty)
                            {
                                if (customntrendering == null)
                                    customntrendering = new NucleotideRendering();

                                customntrendering.Color = customntfill.Value;
                            }

                            double ntfontsize;
                            if (customntfontsize != null && double.TryParse(customntfontsize.Value, out ntfontsize))
                            {
                                if (customntrendering == null)
                                    customntrendering = new NucleotideRendering();

                                customntrendering.FontSize = ntfontsize;
                            }

                            if (customntfontstyle != null &&
                                !customntfontstyle.IsEmpty)
                            {
                                if (customntrendering == null)
                                    customntrendering = new NucleotideRendering();

                                customntrendering.FontStyle = customntfontstyle.Value;
                            }

                            if (customntfontweight != null &&
                                !customntfontweight.IsEmpty)
                            {
                                if (customntrendering == null)
                                    customntrendering = new NucleotideRendering();

                                customntrendering.FontWeight = customntfontweight.Value;
                            }

                            CircleRendering customntcirclerendering = null;
                            XElement customntcirclediameter = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideCircleDiameterLabel);
                            XElement customntcircleedgethickness = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideCircleEdgeThicknessLabel);
                            XElement customntcircleedgefill = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideCircleEdgeFillLabel);
                            XElement customntcirclefill = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideCircleFillLabel);
                            XElement customntcirclefilled = ntpropobj.Element(SequenceMetadata.StructureModelNucleotideCircleFilledLabel);

                            double diameter;
                            if (customntcirclediameter != null && double.TryParse(customntcirclediameter.Value, out diameter) && diameter > 0.0)
                            {
                                if (customntcirclerendering == null)
                                    customntcirclerendering = new CircleRendering();

                                customntcirclerendering.Diameter = diameter;
                            }

                            double edgeThickness;
                            if (customntcircleedgethickness != null && double.TryParse(customntcircleedgethickness.Value, out edgeThickness) && edgeThickness > 0.0)
                            {
                                if (customntcirclerendering == null)
                                    customntcirclerendering = new CircleRendering();

                                customntcirclerendering.EdgeThickness = edgeThickness;
                            }

                            if (customntcircleedgefill != null &&
                                !customntcircleedgefill.IsEmpty)
                            {
                                if (customntcirclerendering == null)
                                    customntcirclerendering = new CircleRendering();

                                customntcirclerendering.EdgeColor = customntcircleedgefill.Value;
                            }

                            if (customntcirclefill != null &&
                                !customntcirclefill.IsEmpty)
                            {
                                if (customntcirclerendering == null)
                                    customntcirclerendering = new CircleRendering();

                                customntcirclerendering.FillColor = customntcirclefill.Value;
                            }

                            bool isCircleFilled = false;
                            if (customntcirclefilled != null && !customntcirclefilled.IsEmpty && bool.TryParse(customntcirclefilled.Value, out isCircleFilled))
                            {
                                if (customntcirclerendering == null)
                                    customntcirclerendering = new CircleRendering();

                                customntcirclerendering.Filled = isCircleFilled;
                            }

                            if (isNtSubstituted)
                            {
                                nt.Rendering = new NucleotideShape(ntRenderingStyle, customntrendering, customntcirclerendering, new List<int>(nt.Index), ntSubstitutionVal);
                            }
                            else
                            {
                                nt.Rendering = new NucleotideShape(ntRenderingStyle, customntrendering, customntcirclerendering, new List<int>(nt.Index));
                            }
                        }
                    }
                }
                retValue.DecomposeStructure();
                return retValue;
            }
            else
            {
                return null; //Invalid formatting for structure model rendering xml.
            }
        }

        public SequenceRendering Rendering { get; private set; }
        public List<StructureModelNucleotide> Sequence { get; private set; }
        public List<StructureModelBasePair> BasePairs { get; private set; }
        public NucleotideRendering NucleotideRenderingAtStructureModelLevel { get; set; }
        public BasePairConnectorShape ConnectorRenderingAtStructureModelLevel { get; set; }
        //public bool LoadingError { get; private set; }

        public override XElement ToXML()
        {
            XElement strmodelxml = new XElement(SequenceMetadata.StructureModelLabel);
            strmodelxml.Add(new XAttribute(SequenceMetadata.StructureModelHasRenderingAttribute, true));
            strmodelxml.Add(new XAttribute(SequenceMetadata.StructureModelSequenceLengthAttribute, SequenceLength));

            StringBuilder xrnasequencebuilder = new StringBuilder();
            foreach (var nt in Sequence)
                xrnasequencebuilder.Append(nt.Nucleotide);
            XElement xrnasequence = new XElement(SequenceMetadata.StructureModelSequenceLabel, xrnasequencebuilder.ToString());

            XElement xrnasequencerendering = new XElement(SequenceMetadata.StructureModelRenderingLabel);
            var renderingMap = from idx in Rendering.RenderingMap.Keys
                               select new XElement(SequenceMetadata.StructureModelRenderingVectorLabel,
                                           new XElement(SequenceMetadata.StructureModelRenderingVectorNucleotideIndexLabel, (idx + 1)),
                                           new XElement(SequenceMetadata.StructureModelRenderingVectorXLabel, Rendering.RenderingMap[idx].CenterX),
                                           new XElement(SequenceMetadata.StructureModelRenderingVectorYLabel, Rendering.RenderingMap[idx].CenterY));
            xrnasequencerendering.Add(renderingMap.ToArray());

            XElement xrnabasepairs = new XElement(SequenceMetadata.StructureModelBasePairsLabel);
            foreach (var basePair in BasePairs.OrderBy(bpObj => bpObj.FivePrimeIndex))
            {
                XElement bpxml = new XElement(SequenceMetadata.StructureModelPairLabel);
                bpxml.Add(new XElement(SequenceMetadata.StructureModelPairFivePrimeIndexLabel, (basePair.FivePrimeIndex + 1)));
                bpxml.Add(new XElement(SequenceMetadata.StructureModelPairThreePrimeIndexLabel, (basePair.ThreePrimeIndex + 1)));

                if(basePair.ConnectorVisible.HasValue)
                    bpxml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorVisibleLabel, basePair.ConnectorVisible.Value));

                if (basePair.ConnectorRendering != null)
                {
                    if (basePair.ConnectorRendering.Style.HasValue)
                        bpxml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorRenderingStyleLabel, basePair.ConnectorRendering.Style.Value));

                    XElement bpConnXml = new XElement(SequenceMetadata.StructureModelPairConnectorLabel);
                    
                    if (basePair.ConnectorRendering.FillColor != null)
                        bpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorFillColorLabel, basePair.ConnectorRendering.FillColor));

                    if (basePair.ConnectorRendering.StrokeColor != null)
                        bpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorStrokeColorLabel, basePair.ConnectorRendering.StrokeColor));

                    if (basePair.ConnectorRendering.Size > 0.0)
                        bpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorSizeLabel, basePair.ConnectorRendering.Size));

                    if (basePair.ConnectorRendering.EdgeThickness > 0.0)
                        bpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorStrokeThicknessLabel, basePair.ConnectorRendering.EdgeThickness));

                    if (basePair.ConnectorRendering.Filled.HasValue)
                        bpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorFilledLabel, basePair.ConnectorRendering.Filled));

                    bpxml.Add(bpConnXml);
                }
                xrnabasepairs.Add(bpxml);
            }

            XElement xrnastructuremodelproperties = new XElement(SequenceMetadata.StructureModelPropertiesLabel);
            if(NucleotideRenderingAtStructureModelLevel != null)
            {
                if(NucleotideRenderingAtStructureModelLevel.Color != null)
                    xrnastructuremodelproperties.Add(new XElement(SequenceMetadata.StructureModelNucleotideFillLabel, NucleotideRenderingAtStructureModelLevel.Color));

                if(NucleotideRenderingAtStructureModelLevel.Font != null)
                    xrnastructuremodelproperties.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontLabel, NucleotideRenderingAtStructureModelLevel.Font));

                if(NucleotideRenderingAtStructureModelLevel.FontStyle != null)
                    xrnastructuremodelproperties.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontStyleLabel, NucleotideRenderingAtStructureModelLevel.FontStyle));

                if(NucleotideRenderingAtStructureModelLevel.FontWeight != null)
                    xrnastructuremodelproperties.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontWeightLabel, NucleotideRenderingAtStructureModelLevel.FontWeight));

                if (NucleotideRenderingAtStructureModelLevel.FontSize > 0.0)
                    xrnastructuremodelproperties.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontSizeLabel, NucleotideRenderingAtStructureModelLevel.FontSize));
            }

            if (ConnectorRenderingAtStructureModelLevel != null)
            {
                if (ConnectorRenderingAtStructureModelLevel.Style.HasValue)
                    xrnastructuremodelproperties.Add(new XElement(SequenceMetadata.StructureModelPairConnectorRenderingStyleLabel, ConnectorRenderingAtStructureModelLevel.Style));

                XElement smbpConnXml = new XElement(SequenceMetadata.StructureModelPairConnectorLabel);

                if (ConnectorRenderingAtStructureModelLevel.FillColor != null)
                    smbpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorFillColorLabel, ConnectorRenderingAtStructureModelLevel.FillColor));

                if (ConnectorRenderingAtStructureModelLevel.StrokeColor != null)
                    smbpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorStrokeColorLabel, ConnectorRenderingAtStructureModelLevel.StrokeColor));

                if (ConnectorRenderingAtStructureModelLevel.Size > 0.0)
                    smbpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorSizeLabel, ConnectorRenderingAtStructureModelLevel.Size));

                if (ConnectorRenderingAtStructureModelLevel.EdgeThickness > 0.0)
                    smbpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorStrokeThicknessLabel, ConnectorRenderingAtStructureModelLevel.EdgeThickness));

                if (ConnectorRenderingAtStructureModelLevel.Filled.HasValue)
                    smbpConnXml.Add(new XElement(SequenceMetadata.StructureModelPairConnectorFilledLabel, ConnectorRenderingAtStructureModelLevel.Filled));

                xrnastructuremodelproperties.Add(smbpConnXml);
            }

            XElement xrnastructuremodenucleotideproperties = new XElement(SequenceMetadata.StructureModelNucleotidePropertiesLabel);
            foreach (var nt in Sequence)
            {
                XElement ntMetadata = new XElement(SequenceMetadata.StructureModelNucleotideLabel);
                ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideIndexLabel, (nt.Index + 1)));
                if(nt.Visible.HasValue)
                    ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideVisibilityLabel, nt.Visible.Value));

                if (nt.Rendering != null)
                {
                    if(nt.Rendering.RenderingType.HasValue)
                        ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideRenderingStyleLabel, nt.Rendering.RenderingType.ToString()));
    
                    if (nt.Rendering.IsTextSubstitution)
                        ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideSubstitutionLabel, nt.Rendering.TextSubstitution));
                    
                    if (nt.Rendering.TextShape != null)
                    {
                        if (nt.Rendering.TextShape.Color != null)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideFillLabel, nt.Rendering.TextShape.Color));

                        if (nt.Rendering.TextShape.Font != null)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontLabel, nt.Rendering.TextShape.Font));

                        if (nt.Rendering.TextShape.FontSize > 0.0)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontSizeLabel, nt.Rendering.TextShape.FontSize));

                        if (nt.Rendering.TextShape.FontStyle != null)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontStyleLabel, nt.Rendering.TextShape.FontStyle));

                        if (nt.Rendering.TextShape.FontWeight != null)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideFontWeightLabel, nt.Rendering.TextShape.FontWeight));
                    }
                
                    if (nt.Rendering.CircleShape != null)
                    {
                        ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideCircleFilledLabel, nt.Rendering.CircleShape.Filled));

                        if (nt.Rendering.CircleShape.Diameter > 0.0)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideCircleDiameterLabel, nt.Rendering.CircleShape.Diameter));

                        if (nt.Rendering.CircleShape.EdgeThickness > 0.0)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideCircleEdgeThicknessLabel, nt.Rendering.CircleShape.EdgeThickness));

                        if (nt.Rendering.CircleShape.FillColor != null)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideCircleFillLabel, nt.Rendering.CircleShape.FillColor));

                        if (nt.Rendering.CircleShape.EdgeColor != null)
                            ntMetadata.Add(new XElement(SequenceMetadata.StructureModelNucleotideCircleEdgeFillLabel, nt.Rendering.CircleShape.EdgeColor));
                    }
                }
                xrnastructuremodenucleotideproperties.Add(ntMetadata);
            }

            strmodelxml.Add(xrnastructuremodelproperties);
            strmodelxml.Add(xrnasequence);
            strmodelxml.Add(xrnasequencerendering);
            strmodelxml.Add(xrnabasepairs);
            strmodelxml.Add(xrnastructuremodenucleotideproperties);
            return strmodelxml;
        }

        public StructureModel2D()
            : base(0, new Dictionary<int, int>())
        {
            Rendering = new SequenceRendering();
            Sequence = new List<StructureModelNucleotide>();
            BasePairs = new List<StructureModelBasePair>();
        }

        public StructureModel2D(int sequenceLength)
            : base(sequenceLength, new Dictionary<int, int>())
        {
            Rendering = new SequenceRendering();
            Sequence = new List<StructureModelNucleotide>();
            BasePairs = new List<StructureModelBasePair>();
        }

        private StructureModel2D(List<string> lines) 
            : base(0, new Dictionary<int,int>())
        {
            Debug.Assert(lines != null);
            _xrnaFileLines = lines;
            Rendering = new SequenceRendering();
            Sequence = new List<StructureModelNucleotide>();
            BasePairs = new List<StructureModelBasePair>();
            if (LoadStructureModelXRNA())
            {
                HandleSequenceElementsXRNA();
                HandleBasePairConnectorsXRNA();
                DecomposeStructure();
            }
        }

        private List<string> _xrnaFileLines;
        private static string XRNA_NTHiddenDefinition = @"#!\sh\s\d+\s\d+";
        private static string XRNA_BPConnectorHiddenDefinition = @"[\d+(-?\d+),?]+\s#!Bn";

        private bool LoadStructureModelXRNA()
        {
            Dictionary<int, int> pairings = new Dictionary<int, int>();
            //1. Pickup the basic sequence elements, base pairs and rendering instructions
            foreach (string line in _xrnaFileLines)
            {
                int index, pairedIndex;
                var renderingVector = NucleotideConnector.CreateFromXRNA(line, out index, out pairedIndex);
                if (renderingVector != null) Rendering.RenderingMap.Add(index, renderingVector);
                if (pairedIndex > -1 && index < pairedIndex)
                {
                    if (!pairings.ContainsKey(index) &&
                        !pairings.ContainsValue(pairedIndex) &&
                        !pairings.ContainsKey(pairedIndex) &&
                        !pairings.ContainsValue(index)) { pairings.Add(index, pairedIndex); }
                    else { return false; } //Consistency error as a position is involved in more than two base pairs
                }
            }
            Pairs = pairings;

            if (Rendering.RenderingMap.Keys.Count <= 0) return false; //I don't know what this is as its not an XRNA Structure Model.

            //2. Setup the sequence object
            var orderedByIndex = Rendering.RenderingMap.Keys.OrderBy(key => key);
            foreach (var index in orderedByIndex)
            {
                Sequence.Add(new StructureModelNucleotide(index, ((NucleotideConnector)Rendering.RenderingMap[index]).Nucleotide));
            }

            if (Rendering.RenderingMap.Keys.Count != Sequence.Count) return false; //Error, we have an invalid nucleotide definition somewhere
            SequenceLength = Sequence.Count;

            //3. Connect consecutive vectors on the rendering map
            for (int i = 0; i < Sequence.Count; i++)
            {
                NucleotideConnector currentPosition = (NucleotideConnector)Rendering.RenderingMap[i];
                if (i < (Sequence.Count - 1)) //The last nucleotide is unconnected, so we check for that
                {
                    if (!Rendering.RenderingMap.ContainsKey(i + 1)) return false; //Error, we are missing the definition for a consecutive nt.
                    NucleotideConnector nextPosition = (NucleotideConnector)Rendering.RenderingMap[i + 1];
                    currentPosition.ConnectTo(nextPosition);
                }
            }
            return true;
        }

        private void HandleBasePairConnectorsXRNA()
        {
            //1. Lets see if we have any base pair connectors defined
            foreach (string line in _xrnaFileLines)
            {
                BasePairConnectorShape bpConn = BasePairConnectorShape.CreateFromXRNA(line);
                if (bpConn != null)
                {
                    //Use a simple LINQ query to identify the base pairs that this connector should be assigned to
                    var basePairs = from bp in Pairs
                                    from fpIdx in bpConn.AssignedTo
                                    where bp.Key == fpIdx
                                    select bp;
                    if (basePairs.Count() == Pairs.Count()) //Default formatting instruction
                    {
                        ConnectorRenderingAtStructureModelLevel = bpConn;
                    }
                    else
                    {
                        foreach (var basePairForConnector in basePairs)
                        {
                            StructureModelBasePair bp = new StructureModelBasePair(basePairForConnector.Key, basePairForConnector.Value, bpConn);
                            BasePairs.Add(bp);
                        }
                    }
                }
            }

            //2. Now we need to create base pair objects for pairings that should be picking up a default connector.
            var remainingBasePairs = Pairs.Select(bp => bp.Key).Except(BasePairs.Select(bpObj => bpObj.FivePrimeIndex));
            foreach (var fivePrimeIdx in remainingBasePairs)
            {
                StructureModelBasePair bp = new StructureModelBasePair(fivePrimeIdx, Pairs[fivePrimeIdx], null);
                BasePairs.Add(bp);
            }

            //3. Determine if any base pairs have been specified to not have their connector visible
            var hiddenBasePairs = _xrnaFileLines.Where(l => Regex.IsMatch(l, XRNA_BPConnectorHiddenDefinition));
            foreach (var hiddenBasePairDef in hiddenBasePairs)
            {
                string[] tokens = Regex.Split(hiddenBasePairDef, " ");
                string[] basePairsToHideRanges = Regex.Split(Regex.Split(tokens[0], "#")[0], ",");
                var basePairsToHide = from token in basePairsToHideRanges
                                      from fpIdx in StructureModel2D.ExpandXRNARange(token, true)
                                      from bp in Pairs
                                      where bp.Key == (fpIdx - 1)
                                      select bp;
                foreach (var bp in basePairsToHide)
                {
                    StructureModelBasePair bpToHide = BasePairs.Where(bpObj => (bpObj.FivePrimeIndex == bp.Key && bpObj.ThreePrimeIndex == bp.Value)).FirstOrDefault();
                    if (bpToHide != null)
                    {
                        bpToHide.ConnectorVisible = false;
                    }
                }
            }

            //4. Determine if any base pairs have nucleotides that are hidden, we should hide the connector too.
            var basePairsWithHiddenNt = from ntObj in Sequence.Where(ntObj => ntObj.Visible == false)
                                        from bpObj in BasePairs
                                        where bpObj.FivePrimeIndex == ntObj.Index ||
                                        bpObj.ThreePrimeIndex == ntObj.Index
                                        select bpObj;
            
            foreach (var basePairWithHiddenNt in basePairsWithHiddenNt)
            {
                StructureModelBasePair bpToHide = BasePairs.Where(bpObj => (bpObj.FivePrimeIndex == basePairWithHiddenNt.FivePrimeIndex && bpObj.ThreePrimeIndex == basePairWithHiddenNt.ThreePrimeIndex)).FirstOrDefault();
                if (bpToHide != null)
                {
                    bpToHide.ConnectorVisible = false;
                }
            }
        }

        private void HandleSequenceElementsXRNA()
        {
            foreach (string line in _xrnaFileLines)
            {
                var ntShape = NucleotideShape.CreateFromXRNA(line);
                if (ntShape != null)
                {
                    Debug.Assert(ntShape.AssignedTo != null); //If this specific shape was created, we should have some nucleotides to assign it to.
                    if (ntShape.AssignedTo.Count() == Sequence.Count) //Default formatting instruction
                    {
                        //Debug.Assert(ntShape.RenderingType == NucleotideShapeType.Text); Since CATUI defaults this, we don't need to check this.
                        Debug.Assert(ntShape.TextShape != null); //We should have a specific shape to use for the nucleotides specified in assignedTo
                        NucleotideRenderingAtStructureModelLevel = ntShape.TextShape;
                    }
                    else
                    {
                        foreach (int ntPos in ntShape.AssignedTo)
                        {
                            int idx = ntPos;
                            if (idx < 0 || idx >= Sequence.Count) continue; //An invalid nt specifier
                            Sequence[idx].Rendering = ntShape;
                        }
                    }
                }
            }

            var hiddenNtSetDefs = _xrnaFileLines.Where(l => Regex.IsMatch(l, XRNA_NTHiddenDefinition));
            foreach (var hiddenNtSet in hiddenNtSetDefs)
            {
                string[] tokens = Regex.Split(hiddenNtSet, " ");
                Debug.Assert(tokens.Length == 4);
                int startIdx, endIdx;
                if (Int32.TryParse(tokens[2], out startIdx) &&
                    Int32.TryParse(tokens[3], out endIdx) &&
                    startIdx <= endIdx &&
                    (startIdx - 1) >= 0 &&
                    (endIdx - 1) < Sequence.Count)
                {
                    IEnumerable<int> hiddenNt = StructureModel2D.ExpandXRNARange(string.Format("{0}-{1}", startIdx - 1, endIdx - 1), true);
                    foreach (var ntIdx in hiddenNt)
                    {
                        StructureModelNucleotide ntSymbol = Sequence[ntIdx];
                        ntSymbol.Visible = false;
                    }
                }
            }
        }


        #region Internals

        internal struct XRNATypeface
        {
            public string Font;
            public string FontStyle;
            public string FontWeight;
        }

        internal static XRNATypeface ParseXRNACode(int xRNACode)
        {
            XRNATypeface retValue;
            switch (xRNACode)
            {
                case 0:
                    retValue.Font = "Helvetica, Arial";
                    retValue.FontStyle = "Normal";
                    retValue.FontWeight = "Regular";
                    break;
                case 1:
                    retValue.Font = "Helvetica, Arial";
                    retValue.FontStyle = "Oblique";
                    retValue.FontWeight = "Regular";
                    break;
                case 2:
                    retValue.Font = "Helvetica, Arial";
                    retValue.FontWeight = "Bold";
                    retValue.FontStyle = "Normal";
                    break;
                case 3:
                    retValue.Font = "Helvetica, Arial";
                    retValue.FontStyle = "Oblique";
                    retValue.FontWeight = "Bold";
                    break;
                case 4:
                    retValue.Font = "Times, Times New Roman";
                    retValue.FontStyle = "Normal";
                    retValue.FontWeight = "Regular";
                    break;
                case 5:
                    retValue.Font = "Times, Times New Roman";
                    retValue.FontStyle = "Italic";
                    retValue.FontWeight = "Regular";
                    break;
                case 6:
                    retValue.Font = "Times, Times New Roman";
                    retValue.FontWeight = "Bold";
                    retValue.FontStyle = "Normal";
                    break;
                case 7:
                    retValue.Font = "Times, Times New Roman";
                    retValue.FontStyle = "Italic";
                    retValue.FontWeight = "Bold";
                    break;
                case 8:
                    retValue.Font = "Courier, Courier New";
                    retValue.FontStyle = "Normal";
                    retValue.FontWeight = "Regular";
                    break;
                case 9:
                    retValue.Font = "Courier, Courier New";
                    retValue.FontStyle = "Oblique";
                    retValue.FontWeight = "Regular";
                    break;
                case 10:
                    retValue.Font = "Courier, Courier New";
                    retValue.FontWeight = "Bold";
                    retValue.FontStyle = "Normal";
                    break;
                case 11:
                    retValue.Font = "Courier, Courier New";
                    retValue.FontStyle = "Oblique";
                    retValue.FontWeight = "Bold";
                    break;
                case 13:
                    retValue.Font = "Symbol";
                    retValue.FontStyle = "Normal";
                    retValue.FontWeight = "Regular";
                    break;
                default:
                    retValue.Font = "Helvetica, Arial";
                    retValue.FontStyle = "Normal";
                    retValue.FontWeight = "Regular";
                    break;
            };
            return retValue;
        }

        internal static IEnumerable<int> ExpandXRNARange(string rangeDef, bool inclusive)
        {
            if (!rangeDef.Contains("-"))
            {
                yield return Int32.Parse(rangeDef);
            }
            else
            {
                string[] ends = Regex.Split(rangeDef, "-");
                int start = Int32.Parse(ends[0]), end = Int32.Parse(ends[1]), count = end - start;
                foreach (int val in Enumerable.Range(start, (inclusive) ? count + 1 : count))
                {
                    yield return val;
                }
            }
        }

        #endregion
    }
}
